package com.dawn.iiotgateway.daemon;

import com.alibaba.fastjson2.JSONObject;
import com.dawn.iiotgateway.entity.Variable;
import com.dawn.iiotgateway.service.IVariableService;
import com.dawn.iiotgateway.utils.Timestamp;
import com.fasterxml.jackson.databind.util.JSONPObject;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
import lombok.Setter;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**
 * 有密码有证书有安全认证模式
 */
@Service
public class OpcUAService {

    //@Getter
    private JSONObject messages;

    @Getter
    private List<Variable> variables;

    @Autowired
    private MqttService mqttService;

    @Autowired
    private IVariableService variableService;
    
    //opc ua服务端地址
    private final static String endPointUrl = "opc.tcp://localhost:53530/OPCUA/SimulationServer";
    private OpcUaClient opcUaClient;

    @PostConstruct
    public void initialOpcTask() {
        try {

            messages = new JSONObject();

            //创建OPC UA客户端
            opcUaClient = createClient();

            //开启连接
            opcUaClient.connect().get();

            // 订阅消息
//            subscribe(opcUaClient);

            // 关闭连接
            //opcUaClient.disconnect().get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Scheduled(initialDelay = 1000, fixedDelay = 5000)
    public void runOpcTask(){

        //System.out.println(Timestamp.now() + "=== OpcUAClient2::runOpcTask begin 1 ===");
        // 写入
        // writeValue(opcUaClient);

        // 读取
        readValue(opcUaClient);

        //System.out.println(Timestamp.now() + "=== OpcUAClient2::runOpcTask end 2 ===");
    }

    /**
     * 创建OPC UA客户端
     * @throws Exception
     */
    private static OpcUaClient createClient() throws Exception {
        Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
        Files.createDirectories(securityTempDir);
        if (!Files.exists(securityTempDir)) {
            throw new Exception("unable to create security dir: " + securityTempDir);
        }

        KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);
        UsernameProvider usernameProvider = new UsernameProvider("shuai", "shuai");

        return OpcUaClient.create(endPointUrl,
                endpoints ->
                        endpoints.stream()
                                .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.Basic256Sha256.getUri()))
//                                .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
                                .findFirst(),
                configBuilder ->
                        configBuilder
                                .setApplicationName(LocalizedText.english("DawnIIoTGateway")) // huazh-01
                                .setApplicationUri("urn:eclipse:milo:client:dawniiotgw") // ns=2:s=huazh-01.device1.data-huazh
                                //访问方式 new AnonymousProvider()
                                .setCertificate(loader.getClientCertificate())
                                .setKeyPair(loader.getClientKeyPair())
                                .setIdentityProvider(usernameProvider)
                                .setRequestTimeout(UInteger.valueOf(5000))
                                .build()
        );
    }

    private static void subscribe(OpcUaClient client) throws Exception {
        //创建发布间隔1000ms的订阅对象
        client.getSubscriptionManager()
                .createSubscription(3000.0)
                .thenAccept(OpcUAService::onSubscribeAccept).get();
    }

    public static void onSubscribeAccept(UaSubscription t) {

        System.out.println(Timestamp.now() + "=== OpcUAClient2::onSubscribeAccept begin 1 ===");

        //节点ns=2;s=test.device2.test2
//                    NodeId nodeId = new NodeId(3, "unit/Peri_I_O.gs_ComToRM.r32_A1_Axis_ActValue");
        NodeId nodeId = new NodeId(3, "temperature/ScalarTypes/Float");
        ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);

        //创建监控的参数
        MonitoringParameters parameters = new MonitoringParameters(UInteger.valueOf(1), 1000.0, null, UInteger.valueOf(10), true);

        //创建监控项请求
        //该请求最后用于创建订阅。
        MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
        List<MonitoredItemCreateRequest> requests = new ArrayList<>();
        requests.add(request);

        //创建监控项，并且注册变量值改变时候的回调函数。
        t.createMonitoredItems(
                TimestampsToReturn.Both,
                requests,
                OpcUAService::onItemCreationCallback
        );

        System.out.println(Timestamp.now() + "=== OpcUAClient2::onSubscribeAccept end 2 ===");
    }

    public static void onItemCreationCallback(UaMonitoredItem item, int id) {
        item.setValueConsumer((it, val) -> {
            System.out.println(Timestamp.now() + "=====订阅nodeid====== :" + it.getReadValueId().getNodeId());
            System.out.println(Timestamp.now() + "=====订阅value===== :" + val.getValue().getValue());
        });
    }

    /**
     * 读取 Opc 数据
     * @param client
     */
    public void readValue(OpcUaClient client) {


        try {
            System.out.println(Timestamp.now() + "=== OpcUAClient2::readValue begin 1 ===");

            ArrayList<NodeId> nodeIds = new ArrayList<>();
            variables = variableService.list();
            DataValue value;

            messages.clear();
            List<Variable> vlist = new ArrayList<>();

            for (Variable variable : variables) {

                String now = Timestamp.now();
                value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, variable.getNodeid())).get();
                Object value1 = value.getValue().getValue();
                if(value1!=null){
                    variable.setValue(value1.toString());
                    variable.setDtime(now);
                    vlist.add(variable);
                    System.out.println(String.format("===== 时间:%s, 读取:%s, Val: ====：", variable.getLabel(), now, variable.getValue()));
                }
            }

            /*
            // dissolved_oxygen
            //nodeIds.add(new NodeId(3, 1009));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1009)).get();
            Object value1 = value.getValue().getValue();
            messages.put("dissolved_oxygen", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 dissolved_oxygen ====：" + value1);

            // cod_in
            nodeIds.add(new NodeId(3, 1010));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1010)).get();
            value1 = value.getValue().getValue();
            messages.put("cod_in", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 cod_in ====：" + value1);

            // cod_out
            nodeIds.add(new NodeId(3, 1011));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1011)).get();
            value1 = value.getValue().getValue();
            messages.put("cod_out", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 cod_out ====：" + value1);

            // ph_in
            nodeIds.add(new NodeId(3, 1012));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1012)).get();
            value1 = value.getValue().getValue();
            messages.put("ph_in", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 ph_in ====：" + value1);

            // ph_out
            nodeIds.add(new NodeId(3, 1013));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1013)).get();
            value1 = value.getValue().getValue();
            messages.put("ph_out", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 ph_out ====：" + value1);

            // bod_in
            nodeIds.add(new NodeId(3, 1014));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1014)).get();
            value1 = value.getValue().getValue();
            messages.put("bod_in", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 bod_in ====：" + value1);

            // bod_out
            nodeIds.add(new NodeId(3, 1015));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1015)).get();
            value1 = value.getValue().getValue();
            messages.put("bod_out", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 bod_out ====：" + value1);

            // fan_freq_1
            nodeIds.add(new NodeId(3, 1016));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1016)).get();
            value1 = value.getValue().getValue();
            messages.put("fan_freq_1", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 fan_freq_1 ====：" + value1);

            // fan_freq_2
            nodeIds.add(new NodeId(3, 1017));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1017)).get();
            value1 = value.getValue().getValue();
            messages.put("fan_freq_2", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 fan_freq_2 ====：" + value1);

            // fan_freq_3
            nodeIds.add(new NodeId(3, 1018));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1018)).get();
            value1 = value.getValue().getValue();
            messages.put("fan_freq_3", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 fan_freq_3 ====：" + value1);

            // pump_freq_1
            nodeIds.add(new NodeId(3, 1019));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1019)).get();
            value1 = value.getValue().getValue();
            messages.put("pump_freq_1", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 pump_freq_1 ====：" + value1);

            // pump_freq_2
            nodeIds.add(new NodeId(3, 1020));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1020)).get();
            value1 = value.getValue().getValue();
            messages.put("pump_freq_2", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 pump_freq_2 ====：" + value1);

            // tss
            nodeIds.add(new NodeId(3, 1021));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1021)).get();
            value1 = value.getValue().getValue();
            messages.put("tss", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 tss ====：" + value1);

            // total_nitrogen_in
            nodeIds.add(new NodeId(3, 1022));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1022)).get();
            value1 = value.getValue().getValue();
            messages.put("total_nitrogen_in", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 total_nitrogen_in ====：" + value1);

            // total_nitrogen_out
            nodeIds.add(new NodeId(3, 1023));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1023)).get();
            value1 = value.getValue().getValue();
            messages.put("total_nitrogen_out", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 total_nitrogen_out ====：" + value1);

            // total_phosphorus_in
            nodeIds.add(new NodeId(3, 1024));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1024)).get();
            value1 = value.getValue().getValue();
            messages.put("total_phosphorus_in", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 total_phosphorus_in ====：" + value1);

            // total_phosphorus_out
            nodeIds.add(new NodeId(3, 1025));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1025)).get();
            value1 = value.getValue().getValue();
            messages.put("total_phosphorus_out", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 total_phosphorus_out ====：" + value1);

            // ammonium_nitrogen_in
            nodeIds.add(new NodeId(3, 1027));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1027)).get();
            value1 = value.getValue().getValue();
            messages.put("ammonium_nitrogen_in", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 ammonium_nitrogen_in ====：" + value1);

            // ammonium_nitrogen_out
            nodeIds.add(new NodeId(3, 1028));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1028)).get();
            value1 = value.getValue().getValue();
            messages.put("ammonium_nitrogen_out", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 ammonium_nitrogen_out ====：" + value1);

            // flow_in
            nodeIds.add(new NodeId(3, 1029));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1029)).get();
            value1 = value.getValue().getValue();
            messages.put("flow_in", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 flow_in ====：" + value1);

            // flow_out
            nodeIds.add(new NodeId(3, 1030));
            value = client.readValue(0.0, TimestampsToReturn.Both, new NodeId(3, 1030)).get();
            value1 = value.getValue().getValue();
            messages.put("flow_out", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 flow_out ====：" + value1);

//            List<DataValue> dataValues = client.readValues(0.0, TimestampsToReturn.Both, nodeIds).get();
//            for(DataValue dv : dataValues) {
//                System.out.println(Timestamp.now() + "===== 读取ua* ====：" + dv.getValue().getValue());
//            }


//            NodeId nodeId = new NodeId(3, 1008);
            NodeId nodeId = new NodeId(3, 1008);
            value = client.readValue(0.0, TimestampsToReturn.Both, nodeId).get();
            value1 = value.getValue().getValue();
            messages.put("temperature", value1.toString());
            System.out.println(Timestamp.now() + "===== 读取 temperature ====：" + value1);
*/

            if(vlist.size()>0){
                messages.put("timestamp", Timestamp.now());
                messages.put("station", "江陵德威污水处理厂");
                messages.put("data", variables);

                String jsonString = JSONObject.toJSONString(messages);
                System.out.println(Timestamp.now() + "===== jsonString ====：" + jsonString);

                mqttService.publishMessage(jsonString);
            }else{
                System.out.println(Timestamp.now() + "===== Got No Variable value ====：");
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(Timestamp.now() + "=== OpcUAClient2::readValue end 2 ===");
    }

    public static void writeValue(OpcUaClient client) {
        try {


            //创建变量节点 test.device2.test2
            NodeId nodeId = new NodeId(3, 1008);

            //uda3 boolean
            Short value = 17;
            //创建Variant对象和DataValue对象
            Variant v = new Variant(value);
            DataValue dataValue = new DataValue(v, null, null);

            StatusCode statusCode = client.writeValue(nodeId, dataValue).get();
            System.out.println(statusCode);
            System.out.println("=====写入ua1====：" + statusCode.isGood());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}